﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BaseDao.cs" company="CodePlex">
//   Copyright © CodePlex 2012
// </copyright>
// <summary>
//   Defines the BaseDao type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace DataFramework
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;

    using DataFramework.Attributes;
    using DataFramework.Factories;
    using DataFramework.Interfaces;
    using DataFramework.QueryConditions;
    using DataFramework.QueryUtilities;

    /// <summary>
    /// The base DAO that provides CRUD functionality to it's children.
    /// </summary>
    /// <typeparam name="T">T must be a class with a parameterless constructor.</typeparam>
    public abstract class BaseDao<T> : IDao<T> where T : class, new()
    {
        /// <summary>
        /// Member variable to track if the connection was opened in this DAO.
        /// </summary>
        private bool openedConnection;

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseDao&lt;T&gt;"/> class.
        /// </summary>
        protected BaseDao()
        {
            if (!DatabaseConfiguration.IsConfigured)
            {
                DatabaseConfiguration.Configure();
            }
        }

        /// <summary>
        /// Loads all.
        /// </summary>
        /// <returns>An enumerable list of items of type T.</returns>
        public virtual IEnumerable<T> LoadAll() 
        {
            return LoadAll(null);
        }

        /// <summary>
        /// Loads all.
        /// </summary>
        /// <param name="conditions">The conditions.</param>
        /// <returns>An enumerable list of items of type T.</returns>
        public virtual IEnumerable<T> LoadAll(IEnumerable<BaseQueryCondition> conditions) 
        {
            var tableMapping = typeof(T).GetCustomAttributes(true).OfType<TableMapping>().FirstOrDefault();
            IEnumerable<T> returnItems = null;

            if (tableMapping != null)
            {
                var utility = QueryUtilityFactory.FetchConfiguredHelper();
                using (var cmd = utility.FetchCorrectCommand())
                {
                    utility.BuildSelectStatementForItems(tableMapping.TableName, cmd, typeof(T), conditions);

                    returnItems = ExecuteCommandReadingQuery<T>(cmd);
                }
            }

            return returnItems ?? new List<T>();
        }

        /// <summary>
        /// Loads all as table.
        /// </summary>
        /// <returns>A table containing the database data.</returns>
        public virtual DataTable LoadAllAsTable() 
        {
            return this.LoadAllAsTable(null);
        }

        /// <summary>
        /// Loads all as table.
        /// </summary>
        /// <param name="conditions">The conditions.</param>
        /// <returns>A table containing the database data.</returns>
        public virtual DataTable LoadAllAsTable(IEnumerable<BaseQueryCondition> conditions)
        {
            var tableMapping = typeof(T).GetCustomAttributes(true).OfType<TableMapping>().FirstOrDefault();
            DataTable returnTable = null;

            if (tableMapping != null)
            {
                var utility = QueryUtilityFactory.FetchConfiguredHelper();
                using (var cmd = utility.FetchCorrectCommand())
                {
                    utility.BuildSelectStatementForItems(tableMapping.TableName, cmd, typeof(T), conditions);

                    var dataAdapter = BaseQueryUtility.FetchCorrectDataAdapter();
                    returnTable = this.ExecuteCommandReadingQueryForTable(cmd, dataAdapter);
                }
            }

            return returnTable ?? new DataTable();
        }

        /// <summary>
        /// Adds the specified item.
        /// </summary>
        /// <param name="item">The item to be added.</param>
        public virtual void Add(T item)
        {
            if (item == null)
            {
                return;
            }

            var tableMapping = item.GetType().GetCustomAttributes(true).OfType<TableMapping>().FirstOrDefault();
            if (tableMapping != null)
            {
                var utility = QueryUtilityFactory.FetchConfiguredHelper();
                using (var cmd = utility.FetchCorrectCommand())
                {
                    utility.BuildInsertStatementForItem(item, tableMapping.TableName, cmd);

                    this.ExecuteCommandNonQuery(cmd);
                }
            }
        }

        /// <summary>
        /// Removes the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        public virtual void Remove(T item)
        {
            if (item == null)
            {
                return;
            }

            var tableMapping = item.GetType().GetCustomAttributes(true).OfType<TableMapping>().FirstOrDefault();
            if (tableMapping != null)
            {
                var utility = QueryUtilityFactory.FetchConfiguredHelper();
                using (var cmd = utility.FetchCorrectCommand())
                {
                    utility.BuildDeleteStatementForItem(item, tableMapping.TableName, cmd);
                    
                    this.ExecuteCommandNonQuery(cmd);
                }
            }
        }

        /// <summary>
        /// Updates the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        public virtual void Update(T item)
        {
            if (item == null)
            {
                return;
            }

            var tableMapping = item.GetType().GetCustomAttributes(true).OfType<TableMapping>().FirstOrDefault();
            if (tableMapping != null)
            {
                var utility = QueryUtilityFactory.FetchConfiguredHelper();
                using (var cmd = utility.FetchCorrectCommand())
                {
                    utility.BuildUpdateStatementForItem(item, tableMapping.TableName, cmd);

                    this.ExecuteCommandNonQuery(cmd);
                }
            }
        }

        /// <summary>
        /// Executes the command non query.
        /// </summary>
        /// <param name="command">The command.</param>
        public virtual void ExecuteCommandNonQuery(IDbCommand command)
        {
            if (TransactionManager.Transaction != null)
            {
                command.Transaction = TransactionManager.Transaction;
            }

            command.Connection = TransactionManager.Connection;

            if (TransactionManager.Connection.State == ConnectionState.Closed)
            {
                TransactionManager.Connection.Open();
                this.openedConnection = true;
            }

            command.ExecuteNonQuery();

            if (this.openedConnection)
            {
                TransactionManager.Connection.Close();
                this.openedConnection = false;
            }
        }

        /// <summary>
        /// Executes the command reading query.
        /// </summary>
        /// <typeparam name="TV">The type of the V.</typeparam>
        /// <param name="command">The command.</param>
        /// <returns>An enumerable list of items of type T.</returns>
        public virtual IEnumerable<T> ExecuteCommandReadingQuery<TV>(IDbCommand command)
        {
            var returnItems = new List<T>();
            if (TransactionManager.Transaction != null)
            {
                command.Transaction = TransactionManager.Transaction;
            }

            command.Connection = TransactionManager.Connection;

            if (TransactionManager.Connection.State == ConnectionState.Closed)
            {
                TransactionManager.Connection.Open();
                this.openedConnection = true;
            }

            using (IDataReader r = command.ExecuteReader())
            {
                while (r.Read())
                {
                    var item = new T();
                    var properties = item.GetType().GetProperties();
                    foreach (var property in properties)
                    {
                        var customAttribute =
                            property.GetCustomAttributes(true).OfType<ColumnMapping>().FirstOrDefault();
                        if (customAttribute != null)
                        {
                            // TODO: Create utility for this.
                            var value = r[customAttribute.ColumnName];
                            if (customAttribute.DbType == DbType.Binary && property.PropertyType == typeof(Guid))
                            {
                                value = new Guid((byte[])value);
                            }

                            if (customAttribute.DbType == DbType.Int32 && property.PropertyType == typeof(uint))
                            {
                                value = Convert.ToUInt32(value);
                            }

                            if (customAttribute.DbType == DbType.Boolean && property.PropertyType == typeof(bool))
                            {
                                value = Convert.ToBoolean(value);
                            }

                            if (customAttribute.DbType == DbType.Decimal && property.PropertyType == typeof(decimal))
                            {
                                value = Convert.ToDecimal(value);
                            }

                            if (value == DBNull.Value)
                            {
                                value = null;
                            }

                            property.SetValue(item, value, null);
                        }
                    }

                    returnItems.Add(item);
                }
            }

            if (this.openedConnection)
            {
                TransactionManager.Connection.Close();
                this.openedConnection = false;
            }

            return returnItems;
        }

        /// <summary>
        /// Executes the command reading query for table.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="dataAdapter">The data adapter.</param>
        /// <returns>A data table.</returns>
        public virtual DataTable ExecuteCommandReadingQueryForTable(IDbCommand command, IDbDataAdapter dataAdapter)
        {
            var tempDataSet = new DataSet();
            if (TransactionManager.Transaction != null)
            {
                command.Transaction = TransactionManager.Transaction;
            }

            command.Connection = TransactionManager.Connection;

            if (TransactionManager.Connection.State == ConnectionState.Closed)
            {
                TransactionManager.Connection.Open();
                this.openedConnection = true;
            }

            dataAdapter.SelectCommand = command;
            dataAdapter.Fill(tempDataSet);
            var returnTable = tempDataSet.Tables[0];

            if (this.openedConnection)
            {
                TransactionManager.Connection.Close();
                this.openedConnection = false;
            }

            // Rename columns so they resemble what would be passed back in the objects.
            var properties = typeof(T).GetProperties(); 
            foreach (var property in properties)
            {
                var customAttribute = property.GetCustomAttributes(true).OfType<ColumnMapping>().FirstOrDefault();
                if (customAttribute != null)
                {
                    foreach (DataColumn column in returnTable.Columns)
                    {
                        if (column.ColumnName.ToLowerInvariant() == customAttribute.ColumnName.ToLowerInvariant())
                        {
                            column.ColumnName = property.Name;
                            break;
                        }
                    }
                }
            }

            return returnTable;
        }
    }
}
